﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Repo7.BusinessLogic.Utils.Exceptions;
using Repo7.BusinessLogic.Utils;
using System.Net.Sockets;
using System.Net;

namespace Repo7.BusinessLogic.Network.FileTransfer
{
    /// <summary>
    /// State Object for transferring files
    /// </summary>
    internal class AsyncFileTransferStateObject : IDisposable
    {
        #region Properties

        /// <summary>
        /// Client  socket.
        /// </summary> 
        public Socket WorkSocket = null;

        /// <summary>
        /// Size of receive buffer.
        /// </summary>
        public const int BufferSize = 10240;

        /// <summary>
        /// Default Max Message size = 1 megabyte
        /// </summary>
        public const int DefaultMaxMessageSize = 536870912;

        /// <summary>
        /// Receive buffer.
        /// </summary>
        public byte[] FileBuffer;

        /// <summary>
        /// Message Length.
        /// </summary>
        public long? FileLength;

        /// <summary>
        /// Bytes received.
        /// </summary>
        public int BytesReceived;

        /// <summary>
        /// Maximum message size.
        /// </summary>
        public long MaxMessageSize { get; private set; }

        /// <summary>
        /// Received data string.
        /// </summary>
        public FileStream FileOutStream;

        /// <summary>
        /// File Name
        /// </summary>
        public string FileName;

        /// <summary>
        /// Method to be called after file is received
        /// </summary>
        public Action<IPEndPoint, string> AfterReceiveFile;

        #endregion

        #region Constructor

        /// <summary>
        /// Constructor
        /// </summary>
        public AsyncFileTransferStateObject(int? maxMessageSize = null)
        {
            if (maxMessageSize.HasValue)
            {
                MaxMessageSize = maxMessageSize.Value;
            }
            else
            {
                MaxMessageSize = DefaultMaxMessageSize;
            }

            FileBuffer = new byte[BufferSize];
            FileLength = null;
            BytesReceived = 0;
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Gets the first sizeof(int) bytes representing the size of the message and sets the MessageLenght property. 
        /// </summary>
        public int GetMessageParameters(string repoLocation)
        {
            byte[] sizeNameArray = FileBuffer.Take(sizeof(int)).ToArray();
            int nameSize = BitConverter.ToInt32(sizeNameArray, 0);
            char[] fileNameChars = new char[nameSize];
            Buffer.BlockCopy(FileBuffer, sizeof(int), fileNameChars, 0, nameSize * sizeof(char));
            FileName = new string(fileNameChars);
            FileName = string.Format(@"{0}\{1}", repoLocation, FileName);
            
            //Create input file
            FileOutStream = new FileStream(FileName, FileMode.Create, FileAccess.Write);

            byte[] sizeFileArray = FileBuffer.Skip(sizeof(int) + nameSize * sizeof(char)).Take(sizeof(long)).ToArray();
            FileLength = BitConverter.ToInt64(sizeFileArray, 0);

            if (FileLength < 0 || FileLength > MaxMessageSize)
            {
                throw new Repo7FileTransferException(Messages.MessageLengthException);
            }

            return sizeof(int) + nameSize * sizeof(char) + sizeof(long);
        }

        /// <summary>
        /// Write the bytes in the buffer to the memory stream
        /// </summary>
        /// <param name="offset">bytes offset</param>
        /// <param name="bytesRead">number of bytes received</param>
        public void WriteBytesToFile(int offset, int bytesRead)
        {
            FileOutStream.Write(FileBuffer, offset, bytesRead - offset);
            BytesReceived += bytesRead - offset;
        }

        /// <summary>
        /// Check if the full message has been received
        /// </summary>
        /// <returns>true / false</returns>
        public bool IsMessageReceived()
        {
            if (BytesReceived < FileLength)
            {
                return false;
            }
            else if (BytesReceived > FileLength)
            {
                throw new Repo7FileTransferException(Messages.ReceivedBytesExceedMessageLenght);
            }
            return true;
        }

        #endregion

        #region IDisposable Implementation

        // NOTE: Leave out the finalizer altogether if this class doesn't 
        // own unmanaged resources itself, but leave the other methods
        // exactly as they are. 
        ~AsyncFileTransferStateObject()
        {
            // Finalize calls Dispose(false)
            Dispose(false);
        }

        // Dispose() calls Dispose(true)
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        // The bulk of the clean-up code is implemented in Dispose(bool)
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // free managed resources
                //Worker Socket
                if (WorkSocket != null)
                {
                    WorkSocket.Close();
                    WorkSocket.Dispose();
                }

                //File Stream
                if (FileOutStream != null)
                {
                    FileOutStream.Close();
                    FileOutStream.Dispose();
                }
            }
        }

        #endregion
    }
}
